package emby;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;

import emby.lang.Block;
import emby.lang.Scope;
import embyparse.Parser;
import embyparse.TreeToCode;
import embyparse.tokenizer.StringTokenizer;
import embyparse.tokenizer.Tokenizer;
import embyparse.util.TokenStream;

/**
 * The Script class is where java and emby will be interfaced.
 * A script object is instantiated by specifying an emby source file to run, with
 * an optional generic type parameter.
 * <br>
 * Before evaluation, fields can be set in the global scope through the put method.
 * <br>
 * Finally, the script can be executed with the eval method, optionally returning
 * the result of the execution.
 *
 * <BR><BR>Sample call from java:
 * <BR>
 * Script &ltVector3D&gt s = new Script &ltVector3D&gt("res//script.emby");
 * <BR>
 * Vector3D v = s.eval();
 * <BR>
 * @author Justin
 *
 * @param <U> the return type of the script
 */
public class Script <U>{

	public static void main(String[] args) throws FileNotFoundException{
		Script<Object[]> script = new Script<Object[]>("res//scriptTest.mb");
		script.put("ARRLENGTH", 5); //i want an array of length 5
		script.put("FILL_VALUE", 3);// with each element initialized to 3
		script.put("MULTIPLIER",21);// and then multiplied by 21

		System.out.println("Emby returns:");
		Object[] result = script.eval();
		System.out.println(Arrays.toString(result));

		System.out.println("---");
	}

	/**
	 * Directly evaluates emby source code within an isolated scope.
	 * @param code The code to be evalutated
	 * @return the result of evaluation
	 */
	public static <E> E  eval(String code){
			return eval(code, new Scope());
	}

	/**
	 * Returns the result of evaluating the string code.
	 * Returns null if the object returned not of type E and a class cast exception occured.
	 * @param code
	 * @param s
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <E> E eval(String code, Scope s){
		StringTokenizer t = new StringTokenizer(-1, code);
		Parser p = new Parser(t);
		p.parse();
		ASTNode[] parsed = TreeToCode.treeToNodes(p.getParseStack());
		Object returnVal = null;
		if( parsed.length==1){
			returnVal = parsed[0].eval(s);
		}else{
			Block b = new Block(parsed);
			returnVal = b.eval(s);
		}
		
		try {
			return (E) returnVal;
		}catch(ClassCastException e) {
			e.printStackTrace();
			return null;
		}
		
	}

	Block block;
	//public final static boolean autocompile = false;
	private Scope scope;

	public Script(String filepath) throws FileNotFoundException{
		File f = EnvironmentPaths.findEmbyFile(filepath);
		TokenStream t = (new Tokenizer(f));
		Parser p = new Parser(t);
		p.parse();
		ASTNode[] parsed = TreeToCode.treeToNodes(p.getParseStack());
		block= new Block(parsed);
		scope = new Scope();
		t.close();
	}

	/**
	 * Set a field withing the global scope before execution of the target script.
	 * @param varname
	 * @param o
	 */
	public void put(String varname, Object o){
		scope.setVar(varname, o);
	}

	/**
	 * @see public U eval(Scope s)
	 * @return
	 */
	public U eval(){
		return eval(scope);
	}

	/**
	 * Evaluates a script and optionally returns a result.
	 * <br>
	 * Returns null if a class cast exception was thrown.
	 * @param s
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public U eval(Scope s){
		Object o = block.eval(s);
		scope = new Scope();
		try{
		return (U)o;
		}catch(ClassCastException e){
			return null;
		}
	}
}
